C++17 新特性
目录
- 结构化绑定 (Structured Bindings)
if
和switch
语句初始化 (Initialization inif
andswitch
)- 内联变量 (Inline Variables)
constexpr
Lambda 表达式- 类模板参数推导 (Class Template Argument Deduction)
std::variant
std::optional
std::any
std::string_view
- 文件系统库 (File System Library)
- 其他特性
- 总结
1. 结构化绑定 (Structured Bindings)
C++17引入了结构化绑定,允许你将一个结构体、类、数组或元组的成员直接绑定到多个变量上。
C++17 之前:
std::pair<int, std::string> getStudent() {
return {123, "Alice"};
}
auto student = getStudent();
int id = student.first;
std::string name = student.second;
C++17:
std::pair<int, std::string> getStudent() {
return {123, "Alice"};
}
auto [id, name] = getStudent(); // 使用结构化绑定
示例:
#include <iostream>
#include <tuple>
#include <string>
std::tuple<int, double, std::string> getPerson() {
return std::make_tuple(25, 1.75, "Bob");
}
int main() {
auto [age, height, name] = getPerson();
std::cout << "Age: " << age << std::endl;
std::cout << "Height: " << height << std::endl;
std::cout << "Name: " << name << std::endl;
return 0;
}
结构化绑定可以与 const
、&
以及 &&
结合:
const auto& [constId, constName] = getStudentInfo();
2. if
和 switch
语句初始化
C++17 允许在 if
和 switch
语句中直接初始化变量,这可以有效地限制变量的作用域。
C++17 之前:
{ // 需要额外的作用域
auto value = getValue();
if (value > 10) {
// ...
}
}
C++17:
if (auto value = getValue(); value > 10) {
// ...
}
switch (auto status = getStatus(); status) {
case Success: // ...
case Failure: // ...
}
示例:
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
// 在 if 语句中初始化并查找元素
if (auto it = std::find(numbers.begin(), numbers.end(), 3); it != numbers.end()) {
std::cout << "Found at index: " << std::distance(numbers.begin(), it) << std::endl;
} else {
std::cout << "Not found" << std::endl;
}
// 在 switch 语句中初始化状态码
switch (int status = 0; status) // 这里故意使用分号来表示初始化语句
{
case 0:
std::cout << "Status: OK" << std::endl;
break;
case 1:
std::cout << "Status: Warning" << std::endl;
break;
default:
std::cout << "Status: Error" << std::endl;
}
return 0;
}
注意事项: if
和switch
中的初始化语句和条件之间使用分号 ;
分隔。
3. 内联变量 (Inline Variables)
C++17 允许在头文件中定义内联变量,类似于内联函数。这对于在头文件中定义静态成员变量或全局变量非常有用。
C++17 之前:
// header.h
extern int myGlobal; // 声明
// source.cpp
int myGlobal = 42; // 定义
C++17:
// header.h
inline int myGlobal = 42; // 声明并定义
示例:
// my_class.h
#ifndef MY_CLASS_H
#define MY_CLASS_H
#include <string>
struct MyClass {
static inline std::string version = "1.0"; // 内联静态成员变量
};
#endif
// main.cpp
#include <iostream>
#include "my_class.h"
int main() {
std::cout << "Version: " << MyClass::version << std::endl;
return 0;
}
4. constexpr
Lambda 表达式
C++17 允许 Lambda 表达式被声明为 constexpr
,这意味着它们可以在编译时执行。 即使没有显式声明constexpr
, 如果Lambda表达式满足constexpr
函数的要求,它也可能是constexpr
的.
auto square = [](int n) constexpr {
return n * n;
};
static_assert(square(5) == 25);
示例:
#include <iostream>
#include <array>
int main() {
constexpr auto increment = [](int n) constexpr {
return n + 1;
};
// 在编译时计算数组大小
std::array<int, increment(5)> arr;
std::cout << "Array size: " << arr.size() << std::endl; // 输出 6
constexpr int result = increment(10); //编译时计算
std::cout << result << std::endl;
return 0;
}
注意:
constexpr
lambda 的捕获必须是常量表达式。- 隐式
constexpr
lambda: 如果一个 lambda 表达式满足constexpr
函数的所有要求,即使没有显式地声明为constexpr
,它也可能是constexpr
的。
5. 类模板参数推导 (Class Template Argument Deduction)
C++17 允许编译器自动推导类模板的参数,类似于函数模板的参数推导。
C++17 之前:
std::pair<int, double> p(1, 2.5);
std::vector<int> v = {1, 2, 3};
C++17:
std::pair p(1, 2.5);
std::vector v = {1, 2, 3};
std::tuple t(1, 2.5, "hello"); // 无需 <int, double, const char*>
示例:
#include <iostream>
#include <vector>
#include <utility>
int main() {
std::pair p(1, 3.14); // 推导为 std::pair<int, double>
std::vector v{1, 2, 3}; // 推导为 std::vector<int>
std::cout << "Pair: " << p.first << ", " << p.second << std::endl;
std::cout << "Vector: ";
for (int x : v) {
std::cout << x << " ";
}
std::cout << std::endl;
return 0;
}
可以通过提供推导指引来定制推导过程:
template<typename T>
struct MyContainer {
std::vector<T> data;
MyContainer(T value) : data{value} {}
MyContainer(std::initializer_list<T> list) : data(list) {}
};
// 推导指引
template<typename T>
MyContainer(T) -> MyContainer<T>;
6. std::variant
std::variant
提供了一种类型安全的联合体,可以存储多种不同类型的值,但在任何时刻只能存储其中一种类型的值。
#include <variant>
std::variant<int, double, std::string> v;
v = 10; // 存储 int
v = 3.14; // 存储 double
v = "hello"; // 存储 string
// 访问
std::get<int>(v); // 如果 v 不是 int,会抛出 std::bad_variant_access 异常
if (auto p = std::get_if<int>(&v)) { // 安全访问
// ...
}
// 访问者模式
std::visit([](auto&& arg){ std::cout << arg << std::endl; }, v);
优点: * 类型安全,避免了传统union
可能导致的类型错误。 * 可以存储多种不同类型,提高了灵活性。
示例:
#include <iostream>
#include <variant>
#include <string>
int main() {
std::variant<int, double, std::string> myVar;
myVar = 10; // 现在存储 int
std::cout << "Int value: " << std::get<int>(myVar) << std::endl;
myVar = 3.14; // 现在存储 double
std::cout << "Double value: " << std::get<double>(myVar) << std::endl;
myVar = "Hello"; // 现在存储 string
// 访问时需要小心处理异常,或使用 std::get_if
if (auto p = std::get_if<std::string>(&myVar)) {
std::cout << "String value: " << *p << std::endl;
}
//使用访问者模式
std::visit([](auto&& arg){
std::cout << "Variant value: " << arg << std::endl;
}, myVar);
return 0;
}
7. std::optional
std::optional
表示一个可能存在也可能不存在的值。它类似于指针,但更安全,因为它不会有空指针解引用的风险。
#include <optional>
std::optional<int> getValue(bool valid) {
if (valid) {
return 42;
} else {
return std::nullopt; // 表示不存在
}
}
auto result = getValue(true);
if (result) { // 检查是否存在
std::cout << *result << std::endl; // 安全访问
}
if (result.has_value())
{
std::cout << result.value() << std::endl;
}
result.value_or(0); //提供默认值
示例:
#include <iostream>
#include <optional>
#include <string>
std::optional<std::string> getName(bool valid) {
if (valid) {
return "Alice";
} else {
return std::nullopt;
}
}
int main() {
auto name1 = getName(true);
if (name1) {
std::cout << "Name: " << *name1 << std::endl; // 安全访问
} else {
std::cout << "Name not available." << std::endl;
}
auto name2 = getName(false);
if (name2.has_value()) {
std::cout << "Name: " << name2.value() << std::endl;
} else {
std::cout << "Name not available." << std::endl;
}
std::cout << "Default name: " << name2.value_or("Default") << std::endl; // 提供默认值
return 0;
}
8. std::any
std::any
可以存储任何类型的值,类似于 void*
,但类型安全。
#include <any>
#include <iostream>
std::any a = 1;
a = "hello";
a = std::string("world");
//访问:
std::any_cast<int>(a); // 如果 a 不是 int, 会抛出异常
if (auto p = std::any_cast<int>(&a)) //安全的形式
{
}
示例:
#include <iostream>
#include <any>
#include <string>
#include <vector>
int main() {
std::any value;
value = 42; // 存储 int
std::cout << "Int value: " << std::any_cast<int>(value) << std::endl;
value = std::string("Hello"); // 存储 string
if (auto p = std::any_cast<std::string>(&value)) {
std::cout << "String value: " << *p << std::endl; //安全访问
}
value = std::vector<int>{1, 2, 3};
return 0;
}
9. std::string_view
std::string_view
提供了一个字符串的非拥有视图,它可以指向一个 std::string
、字符数组或字符串字面量,而无需复制字符串内容。
#include <string_view>
void print(std::string_view sv) { // 接受 string_view
std::cout << sv << std::endl;
}
std::string s = "hello";
print(s); // 传入 std::string
print("world"); // 传入字符串字面量
优点:
- 避免不必要的字符串拷贝,提高性能。
- 可以统一处理多种字符串类型。
示例:
#include <iostream>
#include <string_view>
#include <string>
void printString(std::string_view str) {
std::cout << "String: " << str << std::endl;
std::cout << "Size: " << str.size() << std::endl;
std::cout << "First character: " << str[0] << std::endl;
//str[0] = 'a'; 不要尝试修改, string_view 本身并不拥有数据
}
int main() {
std::string s = "Hello, world!";
std::string_view sv1 = s; // 指向 std::string
std::string_view sv2 = "This is a literal"; // 指向字符串字面量
const char* cstr = "C-style string";
std::string_view sv3 = cstr;
printString(sv1);
printString(sv2);
printString(sv3);
return 0;
}
注意事项:
std::string_view
不拥有字符串数据,因此要确保它所指向的字符串在string_view
的生命周期内有效。- 不要尝试通过
std::string_view
修改字符串内容, 因为他可能指向常量字符串字面量.
10. 文件系统库 (File System Library)
C++17 引入了 <filesystem>
库,提供了一组用于操作文件和目录的类和函数。
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;
int main() {
fs::path p = "my_directory";
fs::create_directory(p); // 创建目录
fs::path file = p / "my_file.txt"; // 路径拼接
// 写入文件
// ...
if (fs::exists(file)) {
std::cout << "File size: " << fs::file_size(file) << std::endl;
fs::remove(file); // 删除文件
}
fs::remove(p);
}
主要功能:
- 路径操作 (拼接、分解、规范化等)
fs::path
- 目录操作 (创建、删除、遍历)
fs::create_directory
,fs::remove
,fs::directory_iterator
- 文件操作 (查询属性、复制、移动、删除)
fs::exists
,fs::file_size
,fs::copy_file
,fs::rename
- 权限管理
fs::permissions
- 错误处理
fs::filesystem_error
示例:
#include <iostream>
#include <fstream>
#include <filesystem>
namespace fs = std::filesystem;
int main() {
fs::path currentDir = fs::current_path();
std::cout << "Current directory: " << currentDir << std::endl;
fs::path newDir = currentDir / "my_directory";
fs::create_directory(newDir); // 创建子目录
std::cout << "Created directory: " << newDir << std::endl;
fs::path filePath = newDir / "my_file.txt";
std::ofstream outFile(filePath); // 创建并写入文件
outFile << "Hello, file system!" << std::endl;
outFile.close();
std::cout << "File created: " << filePath << std::endl;
std::cout << "File size: " << fs::file_size(filePath) << " bytes" << std::endl;
// 遍历目录
std::cout << "Files in " << newDir << ":" << std::endl;
for (const auto& entry : fs::directory_iterator(newDir)) {
std::cout << entry.path() << std::endl;
}
// 删除文件和目录
fs::remove(filePath);
fs::remove(newDir);
std::cout << "File and directory removed." << std::endl;
return 0;
}
11. 其他特性
简化的嵌套命名空间定义:
c++// C++17 之前 namespace A { namespace B { namespace C { // ... } } } // C++17 namespace A::B::C { // ... }
新的求值顺序保证: C++17 规定了某些表达式的求值顺序,以减少未定义行为的可能性。
__has_include
预处理表达式:用于检查头文件是否存在.保证的复制消除(Guaranteed copy elision):
struct MyType {
MyType() { std::cout << "Constructor\n"; }
MyType(const MyType&) { std::cout << "Copy constructor\n"; }
};
MyType createMyType() {
return MyType(); // 这里不会调用复制构造函数,直接在返回值处构造对象
}
int main()
{
MyType obj = createMyType();
}
- 更严格的表达式求值顺序: C++17 对表达式的求值顺序做了更严格的规定, 以减少未定义行为。
12. 总结
C++17 带来了许多令人兴奋的新特性,这些特性使 C++ 编程更加现代化、高效和安全。
- 结构化绑定 简化了从复杂数据结构中提取数据的过程。
if
和switch
初始化 增强了代码的可读性和安全性。- 内联变量 简化了头文件中的变量定义。
constexpr
Lambda 扩展了编译时计算的能力。- 类模板参数推导 减少了模板代码的冗余。
std::variant
、std::optional
和std::any
提供了更安全、更灵活的数据存储方式。std::string_view
提高了字符串处理的效率。- 文件系统库 提供了跨平台的文件和目录操作接口。
- 其他特性 简化命名空间, 减少未定义行为等。
希望通过本节课的学习,大家能够掌握 C++17 的新特性,并在实际开发中灵活运用,写出更现代、更优雅、更高效的 C++ 代码。
课后作业:统计单词频率
题目描述:
给定一个文本文件(input.txt
),其中包含多行英文文本。编写一个C++程序,统计文件中每个单词出现的频率,并按频率从高到低输出结果。
要求:
- 程序需要从文件读取文本。
- 忽略标点符号(如
.
,,
,!
,?
等),并将所有单词转换为小写。 - 使用C++17的以下特性:
- 文件系统库 (filesystem):用于文件操作。
- 结构化绑定 (structured bindings):用于遍历
std::map
。 if
语句初始化 (if statement with initializer):用于在循环内部更简洁地处理插入/更新操作。std::string_view
: 避免不必要的字符串拷贝.
输出:
程序应输出一个单词频率列表,每行包含一个单词及其出现的次数,按频率从高到低排序。例如:
the: 15
and: 10
to: 8
a: 7
of: 6
...
代码实现 (C++17):
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <map>
#include <vector>
#include <algorithm>
#include <cctype>
#include <filesystem>
#include <string_view>
namespace fs = std::filesystem;
// 函数:将字符串转换为小写并移除标点符号
std::string cleanWord(std::string_view word) {
std::string result;
for (char c : word) {
if (std::isalpha(c)) { // 检查是否是字母
result += std::tolower(c);
}
}
return result;
}
int main() {
fs::path filePath = "input.txt"; // 文件路径
// 检查文件是否存在
if (!fs::exists(filePath))
{
std::cerr << "Error: File not found: " << filePath << std::endl;
return 1;
}
std::ifstream file(filePath);
if (!file.is_open()) {
std::cerr << "Error opening file: " << filePath << std::endl;
return 1;
}
std::map<std::string, int> wordCounts;
std::string line;
size_t totalWords = 0; // 用于统计单词总数 (可选)
while (std::getline(file, line)) {
std::istringstream lineStream(line);
std::string word;
while (lineStream >> word) {
std::string cleaned = cleanWord(word);
if (!cleaned.empty()) { // 确保清理后的单词非空
// 使用 if 语句初始化来简化插入/更新
if (auto [it, inserted] = wordCounts.insert({cleaned, 1}); !inserted) {
it->second++; // 如果已存在,增加计数
}
totalWords++; //(可选)
}
}
}
file.close();
// 将 map 转换为 vector 以便排序
std::vector<std::pair<std::string, int>> sortedCounts(wordCounts.begin(), wordCounts.end());
std::sort(sortedCounts.begin(), sortedCounts.end(),
[](const auto& a, const auto& b) {
return a.second > b.second; // 按频率降序排序
});
// 输出结果
std::cout << "Word frequencies (total " << totalWords << " words):" << std::endl; //(可选)
for (const auto& [word, count] : sortedCounts) { // 使用结构化绑定
std::cout << word << ": " << count << std::endl;
}
return 0;
}